Cloud API
Asynchronous results & notifications
A card transaction takes real-world time: the customer has to present a card, possibly enter a
PIN, and the issuer has to authorise. The Cloud API therefore supports two ways of returning a
result — synchronously in the HTTP response, or asynchronously through a notification sent
to a URL you provide.
Understanding this is essential: an HTTP 202 is not an error, and a transaction that returns
202 is very likely to succeed.
How it works
You control the behaviour with the waitTime query parameter (1–300 seconds, default 20) on
process-transaction and cancel-transaction.
- You call
POST /process-transaction/{terminal-id}?waitTime=20, includingecrParams.notificationUrlin the body. - The terminal starts the transaction. As it progresses, the API POSTs **progressnotifications** to your
notificationUrl. - Then one of two things happens:
| Outcome | HTTP response | Where the result arrives |
|---|---|---|
The terminal finishes within waitTime | 201 (or 200 for cancel-transaction) with the full result in the body | In the HTTP response |
The terminal takes longer than waitTime | 202 Accepted, no result body | Later, as a notification POSTed to your notificationUrl |
Warning
HTTP 202 is not a failure. It means "accepted, still in progress". Do not roll back, retry,
or show an error to the cashier. Wait for the notification, or recover withGET /last-transaction/{terminal-id}.
Progress notifications are sent in both cases — they are not exclusive to the 202 path.
Setting up your notification endpoint
Provide the URL in every transaction request, inside ecrParams:
{
"transactionType": "PURCHASE",
"amount": "1000",
"currency": "978",
"ecrTransactionId": "ECR-mrt1levt-eqi21j",
"ecrParams": {
"ecrId": "POS_01",
"notificationUrl": "https://pos.example.com/webhook/notify/9f2a77c1"
}
}Requirements for the endpoint:
- It must be publicly reachable over HTTPS. The API calls it from the internet, so a
localhostaddress will never work. For local development, expose your machine with a tunnel(for example ngrok or cloudflared) and use the public URL it gives you. - It must accept
POSTwith a JSON body and respond quickly with200. Do any slowprocessing after responding. - It should be hard to guess, for example by including a random token in the path as in theexample above. The notification call itself is not authenticated, so an unguessable path iswhat prevents third parties from posting fake results to it.
- It should be idempotent. You will receive several notifications for the same transaction,and you should tolerate a repeat of one you have already handled.
What you receive
The body is a Notification object:
| Field | Description |
|---|---|
status | The current TransactionStatus: WAITING_FOR_CARD, PIN_REQUIRED, BANK_AUTHORIZATION, or COMPLETED |
ecrId | Your ECR identifier, as sent in ecrParams |
ecrTransactionId | Your transaction id — use this to correlate |
terminalTransactionId | The terminal's transaction id, once assigned |
result | Present on the final notification: the full TransactionResult |
Progress notification
{
"status": "WAITING_FOR_CARD",
"ecrId": "POS_01",
"ecrTransactionId": "ECR-mrt1levt-eqi21j"
}The typical sequence is WAITING_FOR_CARD → PIN_REQUIRED → BANK_AUTHORIZATION →
COMPLETED. Use these to drive a live status display for the cashier. Not every step occurs in
every transaction — a contactless payment under the floor limit may never send PIN_REQUIRED.
Final notification
The COMPLETED notification carries the outcome in result:
{
"status": "COMPLETED",
"ecrId": "POS_01",
"ecrTransactionId": "ECR-mrt1levt-eqi21j",
"terminalTransactionId": "14",
"result": {
"status": "OK",
"responseCode": "000",
"authorizationCode": "213462",
"terminalTransactionId": "14",
"signatureRequired": false,
"forcedOffline": false,
"customerReceipt": "...",
"cashierReceipt": "..."
}
}Read the outcome from result.responseCode: "000" means approved, and authorizationCode
is present only in that case. Any other value is a decline code from the acquirer.
Correlating a notification to your transaction
Match on ecrTransactionId — the identifier you generated and sent on the request. It is the
only value you control and know in advance.
terminalTransactionId is assigned by the terminal and is only known after the fact, so use it
as a fallback. If a notification matches nothing you are tracking, log it and ignore it rather
than failing.
Delivery guarantees
Warning
Notifications are delivered once and are not retried, and the reachability of yournotificationUrlis not validated beforehand. If your endpoint is down, misconfigured, or slow
to respond at that moment, the result is lost — the API will not send it again.
Because of this, never treat the notification as your only source of truth. Always implement the
recovery path below.
Recovering a missed result
If you receive a 202 and no notification arrives, or a request fails with a connection error, ask
the terminal what happened:
GET /last-transaction/{terminal-id}Then verify that
transactionResult.finalTransactionParams.ecrTransactionId matches the transaction you are
recovering.
Warning
The terminal may return the previous (one-before-last) transaction if the original request
failed very late. TheecrTransactionIdcheck is not optional — without it you risk reconciling
against the wrong payment.
Choosing waitTime
| Situation | Suggestion |
|---|---|
| Attended checkout, cashier waiting at the screen | Keep the default 20, and handle the 202 path |
| You want the result in the HTTP response more often | Raise waitTime (maximum 300) |
| You prefer to return control to the POS immediately | Lower waitTime and rely on notifications |
Note
Always set your client/socket timeout abovewaitTime, with a margin. Issuer delays can add
around 30 seconds. If your client times out first you lose a result that was about to arrive,
and you then have to recover it withlast-transaction.
Common pitfalls
| Pitfall | Consequence | Fix |
|---|---|---|
| Treating 202 as an error | Cashier sees a failure for a payment that succeeds | Treat 202 as "in progress" and wait for the notification |
notificationUrl not reachable from the internet | No notification ever arrives | Use a public HTTPS URL; use a tunnel in development |
Client timeout lower than waitTime | Connection dropped just before the result arrives | Set the timeout above waitTime plus a margin |
| Relying only on notifications | Occasional lost results, since there is no retry | Implement last-transaction recovery |
Correlating on terminalTransactionId | Cannot match the early notifications | Correlate on ecrTransactionId |
| Slow notification endpoint | Delivery may fail; there is no second attempt | Respond 200 immediately, process afterwards |